/*
 *      Author: Guannan Ma
 *      @mythmgn
 *      Whatever license applied
 */

#include "base.h"

queue * qInit() {
	queue *pq;
	if (NULL == (pq = (queue *) malloc(sizeof(queue)))){
        setErrStatusCode(MEMORY_MALLOC_FAILURE);
		return NULL;
    }
	else {
        mlock_init(&pq->lock);
		pq->next = NULL;
		pq->content = NULL;
		pq->inUseNum = 0;
		return pq;
	}
}

BOOL qClose(queue *pq, BOOL freeContent) {
    BOOL rev = TRUE;
    if (TRUE != isQueueEmpty(pq)){
        qEmpty(pq, freeContent);
    }
    mlock_destroy(&pq->lock);
    FREEUP(pq);
    return rev;
}

BOOL isQueueEmpty(queue *pq) {
    BOOL rev = FALSE;
    mlock_lock(&pq->lock);
	if (NULL == pq || NULL == pq->next){
		rev = TRUE;
    }
    mlock_unlock(&pq->lock);
    return FALSE;
}

void * qPop(queue *pq) {
	queue *pTemp;
	void *pReturn = NULL;
    mlock_lock(&pq->lock);
	if (TRUE != isQueueEmpty(pq)){
        pReturn = pq->next->content;
        pTemp = pq->next->next;
        FREEUP(pq->next);
        pq->next = pTemp;
        pq->inUseNum -= 1;
    }
	mlock_unlock(&pq->lock);
	return pReturn;
}

/*! @fn
 *
 */
BOOL qPush(queue *pq, void *content) {
    BOOL rev = TRUE;
	queue *pRealPos, *pTemp;
    mlock_lock(&pq->lock);
	pTemp = pq;
	if (NULL == pTemp || NULL == content){
        rev = FALSE;
    }else{
	/* find the position where we can push the content into the queue
	 *  pq indicates this is the node before the insertion pos.
	 *  pq->next=<NEW_QUEUE_ITEM>
	 */
        while (NULL != pTemp->next){
            pTemp = pTemp->next;
        }
        if (NULL == (pRealPos = (queue *) malloc(sizeof(queue))))
            setErrStatusCode(MEMORY_MALLOC_FAILURE);
            rev = FALSE;
    }
    if( rev == TRUE ){
        pTemp->next = pRealPos;
        pRealPos->next = NULL;
        pRealPos->content = content;
        pq->inUseNum += 1;
    }
    mlock_unlock(&pq->lock);
	return rev;
}

BOOL qEmpty(queue *pq, BOOL freeContent) {
	queue *pTemp;
    mlock_lock(&pq->lock);
	while (NULL != pq->next) {
		pTemp = pq->next->next;
		if (TRUE == freeContent)
			FREEUP(pq->next->content);
		FREEUP(pq->next);
		pq = pTemp;
		pq->inUseNum = 0;
	}
    mlock_unlock(&pq->lock);
	return TRUE;
}

int qLength(queue *pq) {
    int rev = -1;
    mlock_lock(&pq->lock);
	if (NULL == pq)
        rev = -1;
	else
	    rev = pq->inUseNum;
    mlock_unlock(&pq->lock);
    return rev;
}

/*
 * Enhanced functions
 */

/*
 *  content->nice should be accessable. Otherwise, the function will fail with FALSE
 */
BOOL exQPushWithOrder(queue *pq, niceContent *content) {
	queue *pRealPos, *pTemp, *pTempNex;
    BOOL rev = TRUE;
    mlock_lock(&pq->lock);
	pTemp = pq;
	ASSERT(content->nice);

	if (NULL == pTemp || NULL == content){
        rev = FALSE;
    }
    else{
        /* find the position where we can push the content into the queue
         *  pq indicates this is the node before the insertion pos.
         *  pq->next=<NEW_QUEUE_ITEM>
         */
        while (NULL != pTemp->next
                && content->nice > ((niceContent*) (pTemp->content))->nice)
            pTemp = pTemp->next;
        if (NULL == (pRealPos = (queue *) malloc(sizeof(queue)))){
            setErrStatusCode(MEMORY_MALLOC_FAILURE);
            rev = FALSE;
        }
    }
    if( rev != FALSE){
        pTempNex = pTemp->next;
        pTemp->next = pRealPos;
        pRealPos->content = content;
        /* If the new added node is placed at the middle of the queue, point its member next
         * to its real next node which should be pTempNex that temporarily stores the address.
         * */
        pRealPos->next=(pTempNex==NULL)?NULL:pTempNex;
        pq->inUseNum += 1;
    }
    mlock_unlock(&pq->lock);
	return rev;
}
